import inspect
from functools import wraps


class Environment:
    def __init__(self):
        self._dependencies = {}
        self._dependencies_factory = {}

    def required(self, func):
        """
        when use this method, func needs to inject dependencies params must be put on
        POSITIONAL_OR_KEYWORD last position or define that be KEYWORD_ONLY.
        """
        @wraps(func)
        async def wrapper(*args, **kwargs):
            params = inspect.signature(func).parameters
            deps = {
                name: dependency
                for name, dependency in self._dependencies.items()
                if name in params
            }
            deps.update({
                name: self._dependencies_factory[name]()
                for name in params.keys()
                if name in self._dependencies_factory
            })
            kwargs.update(deps)
            return await func(*args, **kwargs)

        return wrapper

    def get_dependencies(self, name):
        try:
            return self._dependencies[name]
        except KeyError:
            if name in self._dependencies_factory:
                return self._dependencies_factory[name]()
            return

    def register(self, name):
        def decorator(cls):
            origin__new__ = cls.__new__

            def wrapper(c, *args, **kwargs):
                if name in self._dependencies:
                    return self._dependencies[name]
                instance = origin__new__(c)
                self._dependencies[name] = instance
                return instance

            cls.__new__ = wrapper
            return cls

        return decorator

    def register_manual(self, name, func):
        if name not in self._dependencies_factory:
            self._dependencies_factory[name] = func

    def register_inst_manual(self, name, inst):
        if name not in self._dependencies:
            self._dependencies[name] = inst


ENVIRONMENT = Environment()
